
package org.rust.lang;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class Main
{
  
   static class Node
   {
      private Node parent;
      private String value;
      private List<Node> children;
      private char [] src;//original src prog
      private int srcStart, srcEnd; //index of start and end chars of this node
      private Type type;
      
      
      public enum Type
      {
         namedList, list, item;
      }; 

      public Node(Type type, String value, Node parent)
      {
         this.type = type;
         
         if(type == Type.item || type == Type.namedList)
            assrt(value!=null && !value.isEmpty());
         
         this.value = value;
         this.parent = parent;
         
      }
      
      public Node(Type type, String value, Node parent, char [] src, int start, int end)
      {  
         this.type = type;
         
         if(type == Type.item || type == Type.namedList)
            assrt(value!=null && !value.isEmpty());
         
         this.value = value;
         this.parent = parent;

         this.src = src;
         this.srcStart = start;
         this.srcEnd = end;
      }
      
      private void addChild(Node node)
      {
         if(children == null)
            children = new ArrayList<Node>();
         
         children.add(node);
      }
    
      public String getValue()
      {
        return value;  
      }
      
      public int parse()
      {
         
         /*
          * A program is a tree. Each node in the tree has:
          *
          *    a type
          *    a value (can be null)
          *    a list of children (can be null)
          *
          * type can be:
          * 
          *    namedList
          *    list
          *    item
          * 
          * e.g.
          * 
          * sum(1 mul(5 2) (7 8))
          * 
          * 
          * which in tree form is:
          * 
          *             type        value
          *             ----        -----   
          * sum         namedList   sum   
          *    1        item        1
          *    mul      namedList   mul
          *       5     item        5                   
          *       2     item        2
          *    list     list        
          *       7     item        7
          *       8     item        8
          *       
          * 
          *  Everything else (e.g. higher level functionality) is defined in terms of these nodes.
          *  This is the same as any system like maths or language where one thing is defined
          *  hierarchically in terms of another or one component points to another component.
          *  The low level representation above using names and lists/items is close to that of a machine
          *  which uses addresses and memory or more specifically a stack, heap and registers (all memory)
          *  and some primitive operations (mul, sum, jmp) on the values in that memory.
          *  Its easy to flatten the tree above out to a stack (or list) and then have each node
          *  in the list contain a pointer/address of its first child and each child have a pointer
          *  back to its parent. Once you have a tree flattener that does this you can componentize its 
          *  functionality so that you can then think at the next level up and so on: Composition/trees.
          *  The key thing is that an interface to a component/node/level should be small, simple and consistent. 
          *  This makes it more reliable and more flexible to compose with at the next level up.          
          */
         
         
         int p = srcStart;
         while(p <= srcEnd)
         {  
            if(Character.isWhitespace(src[p]))
            {
               //skip any whitepace to get to next token
               while(p <= srcEnd && Character.isWhitespace(src[p]))
                  p++;
            }
            else if(src[p] == '(')
            {
               //Its a list or a namedList
               //If its an '(' then find its matching ')'
               //create a new child of its contained text and parse it. 
               //But first try and see if this child has a name before the opening '('
               String name = null;
               int nameStart = p - 1;
               if(isIdentifier(src[nameStart]))
               {
                  //go to start of name
                  while(nameStart >= srcStart && isIdentifier(src[nameStart]))
                     nameStart--;
                  name = new String(src, nameStart + 1, p - (nameStart+1));
                  srcStart = nameStart + 1;
                  
               }
               
               int closing = findClosing(p); 
               if(closing == 0)
                  throw new RuntimeException("Cannot find closing ) for |" + this + "|");
               
               if(closing - p > 1)
               {
                  Type type = name==null? Type.list: Type.namedList;
                  Node child = new Node(type, name, this, src, p + 1, closing - 1);
                  addChild(child);
                  p = child.parse();
               }
               else
                  p++;
            }
            else
            {
               //Its an item or a namedList
               //If its an identifier then read it until you get to next whitespace or '(' or ')'
               //and make a child of it
               int childStart = p;
               while(p <= srcEnd && isIdentifier(src[p]))
               {
                  p++;
               }
             
               if(src[p]=='(')
               {
                  //we have come to the end of an identifier but its a '('
                  //e.g. we are at the '('   in "sum(2 3)"
                  //in this case we just skip it as the second branch of this if statement will make
                  //a named list of it
               }
               else
               {
                  //this is an item so construct it
                  int childEnd = p - 1;
                  String value = new String(src, childStart, (childEnd - childStart) + 1);
                  Node child = new Node(Type.item, value, this, src, childStart, childEnd);
                  addChild(child);
                  p++;                  
               }                             
            }
         }
         
         return p + 1; //at the end of the while p == srcEnd. Move it on one to get off this node 
         

      }
      
      public Node evaluate(Node args)
      {  
         if(this.type == Type.item)
            return this;
         
         if(this.type == type.list)
         {
            for (Node child: children)
            {
               child.evaluate(args);
            }
            //TODO: return value of last child call here?
            //like the default return?
            return null;
         }
            
         //its a namedList i.e a call to a function
         
         //SrcNode function = getFunction(value);
         
         //return function.evaluate(args);
         
         return evaluateCall();
         
      }
      
      Node evaluateCall()
      {
         //this node is a call evaluate it
         //it value will be the fn name e.g. "sum" and its children will be the arguments
         if(value.equals("sum"))
            return sum(this, children);

         assrt(false);
         return null;
      }
     
      Node sum(Node caller, List<Node> args)
      {  
         //note that caller is only passes so that we can get the original debug info
         
         int sum = 0;
         for(Node arg: args)
         {
            Node result = arg.evaluate(null);
            if(result.type != Type.item)
               assrt(false, "Bad argument: " + arg.getOriginalSrc() +  " passed to sum. Expected: " + Type.item + " received: " + result.type + " in " + caller.getOriginalSrc());
            
            try
            {
               int val = Integer.parseInt(result.value);
               sum += val;
            }
            catch(NumberFormatException ex)
            {
               assrt(false, "Non integer argument: " + arg.getOriginalSrc() +  " passed to sum in " + caller.getOriginalSrc());
            }
         }
         
         return new Node(Type.item, String.valueOf(sum), null);
      }
     
      
      /**
       * @return true if ch is a not whitespace or brackets
       * i.e. its comething like a, b, 9, < whatever
       */
      boolean isIdentifier(char ch)
      {
         return (!Character.isWhitespace(ch)) && (ch!='(') && (ch!=')');
      }

      public void print()
      {
         print(null);
      }
      public void print(String tabs)
      {
         if(tabs == null)
            tabs = new String();
         
         
         if(this.type == Type.item)
            Main.print("->" + tabs + value);
         else
         {
            //its a list or a named list. Print its title and then print the children
            String title = (this.type == Type.namedList? value: "list" );

            Main.print("->" + tabs + title);

            tabs += "   ";
            for (Node child : children)
            {               
               child.print(tabs);
            }
            
         }
      }
      
   

      public int findClosing(int opening)
      {
         int p = opening + 1;
         int openings = 1;
         while(p <= srcEnd)
         {
            if(src[p] == '(')
               openings++;
            
            if(src[p] == ')')
            {
               openings--;
               if(openings == 0)
                  return p;
            }
            p++;
         }   
         return 0;
      }

    
      public String getOriginalSrc()
      {
         return new String(src, srcStart, (srcEnd - srcStart) + 1);
      }
      
      public String getTag()
      {
         return value;
      }
      
      public String toString()
      {
         
         return "type:" + type + " value:" + value; 
      }
      
   }
   
   public static void assrt(boolean cond)
   {
      assrt(cond, "assrt failed");
   }
   
   public static void assrt(boolean cond, String msg)
   {
      if(!cond)
         throw new RuntimeException("msg");      
   }
   
	public static void main(String[] args) throws IOException
	{

	   String filePath = args[0];
	   print("Running " + filePath);
	   File f = new File(filePath);
      BufferedReader in = new BufferedReader(new FileReader(f));
      
      char[] src = new char[(int)f.length()]; 
      in.read(src);
      
      String header = "";
      String dbg = " "; 
      for(int i = 0; i < src.length; i++ )
      {
         if(i < 10)
            header += "0" + i + " " ;
         else
            header += i + " ";
         
         dbg +=  src[i] + "  " ;
      }
      
      print(header);
      print(dbg);
      
      new Main().execute(src);
	}
	

	public void execute(char[] src)
	{
	  Node srcNode = new Node(Node.Type.list, null, null, src, 0, src.length - 1);
	  srcNode.parse();
	  srcNode.print();

	  Node main = srcNode.children.get(0);
	  Node res = main.evaluate(null);
	  print(res.getValue());
	  
	  int i = 1;
	}
	
	
	public static void print(String s)
	{
	   System.out.println(s);
	}
}